package com.tbit.uqbike.client.util.aliyun;

import cn.hutool.core.date.DateUtil;
import com.aliyun.openservices.aliyun.log.producer.LogProducer;
import com.aliyun.openservices.aliyun.log.producer.Producer;
import com.aliyun.openservices.aliyun.log.producer.ProducerConfig;
import com.aliyun.openservices.aliyun.log.producer.ProjectConfig;
import com.aliyun.openservices.aliyun.log.producer.errors.LogSizeTooLargeException;
import com.aliyun.openservices.aliyun.log.producer.errors.MaxBatchCountExceedException;
import com.aliyun.openservices.aliyun.log.producer.errors.ProducerException;
import com.aliyun.openservices.aliyun.log.producer.errors.TimeoutException;
import com.aliyun.openservices.log.common.LogItem;
import com.tbit.uqbike.client.constant.SystemConstant;
import com.tbit.uqbike.client.dto.EventPutDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * @author: ShiGuang
 * @create: 2021-12-06 16:12
 * @description:
 */
public class AliLogUtil {

    /**
     * 项目名
     */
    final static String project = "tbit-client-test";
    /**
     * 日志库名
     */
    final static String logStore = "tbit-client-test";

    /**
     * 使用指定类初始化日志对象，在日志输出的时候，可以打印出日志信息所在类
     */
    private static final Logger log = LoggerFactory.getLogger(AliLogUtil.class);

    /**
     * ProjectConfig包括服务入口信息以及表征调用者身份的访问凭证
     *
     * @return
     */
    private static ProjectConfig buildProjectConfig(String project, String endpoint, String accessKeyId, String accessKeySecret) {

        return new ProjectConfig(project, endpoint, accessKeyId, accessKeySecret);
    }

    /**
     * 配置发送策略
     * 发送策略的详情见：https://developer.aliyun.com/article/682761
     *
     * @return
     */
    private static ProducerConfig buildProducerConfig() {
        ProducerConfig producerConfig = new ProducerConfig();
        // 单个 producer 实例能缓存的日志大小上限，默认为 100MB,单位是字节
        producerConfig.setTotalSizeInBytes(51200);
        // 如果 producer 可用空间不足，调用者在 send 方法上的最大阻塞时间，默认为 60 秒
        producerConfig.setMaxBlockMs(60);
        // 当一个 ProducerBatch 中缓存的日志大小大于等于 batchSizeThresholdInBytes 时，该 batch 将被发送，默认为 512 KB，最大可设置成 5MB
        producerConfig.setBatchSizeThresholdInBytes(3 * 1024 * 1024);
        // 当一个 ProducerBatch 中缓存的日志条数大于等于 batchCountThreshold 时，该 batch 将被发送，默认为 4096，最大可设置成 40960
        producerConfig.setBatchCountThreshold(40960);
        // 如果某个 ProducerBatch 首次发送失败，能够对其重试的次数，默认为 10 次
        producerConfig.setRetries(10);
        return producerConfig;
    }


    /**
     * 发送日志
     *
     * @param map
     */
    public static void sendLog(Map<String, Object> map, String project, String endpoint, String accessKeyId, String accessKeySecret) {
        if (map.isEmpty()) {
            log.info("发送的日志不能为空");
            return;
        }

        ProducerConfig producerConfig = buildProducerConfig();
        Producer producer = new LogProducer(producerConfig);
        producer.putProjectConfig(buildProjectConfig(project, endpoint, accessKeyId, accessKeySecret));
        // 包装参数
        LogItem logItem = generateLogItem(map);

        try {
            /**
             * topic字段留空默认是“”
             * source字段留空默认是宿主机的 IP
             * shardHash字段留空数据将被随机写入目标 logStore 的某个 shard 中
             */
            producer.send(
                    project,
                    logStore,
                    "topic",
                    "",
                    "logStore",
                    logItem,
                    result -> {
                        if (result.isSuccessful()) {
                            log.info("日志发送成功");
                        } else {
                            log.error(
                                    "日志发送失败, project={}, logStore={}, logItem={}, result={}",
                                    project,
                                    logStore,
                                    logItem.ToJsonString(),
                                    result
                            );
                        }
                    });
        } catch (InterruptedException e) {
            log.warn("当前线程在发送日志期间被中断");
        } catch (Exception e) {
            if (e instanceof MaxBatchCountExceedException) {
                log.error("日志超过了最大批量计数，错误为{}", e);
            } else if (e instanceof LogSizeTooLargeException) {
                log.error("日志大小超过了设置的最大值，错误为{}", e);
            } else if (e instanceof TimeoutException) {
                log.error("为日志分配内存所花费的时间已超时, 错误为{}", e);
            } else {
                log.error("发送日志失败, 错误为：{}", e);
            }
        }

        // 发送完毕，关闭 producer
        try {
            producer.close();
        } catch (InterruptedException e) {
            log.warn("当前线程已被关闭中断");
        } catch (ProducerException e) {
            log.info("关闭 producer，错误为{}", e);
        }

        log.info("所有的日志发送完成");
    }

    /**
     * 发送事件上报日志
     */
    public static void sendLog(EventPutDTO eventPutDTO) {
        if (eventPutDTO == null){
            return;
        }
        sendLog(generateLogItem(eventPutDTO), "client-event-log", "event_log", SystemConstant.ENDPOINT, SystemConstant.ACCESSKEYID, SystemConstant.ACCESSKEYSECRET);
    }

    /**
     * 发送日志
     */
    public static void sendLog(LogItem logItem, String project, String logStore, String endpoint, String accessKeyId, String accessKeySecret) {
        Producer producer = null;
        try {
            ProducerConfig producerConfig = buildProducerConfig();
            producer = new LogProducer(producerConfig);
            producer.putProjectConfig(buildProjectConfig(project, endpoint, accessKeyId, accessKeySecret));
            /**
             * topic字段留空默认是“”
             * source字段留空默认是宿主机的 IP
             * shardHash字段留空数据将被随机写入目标 logStore 的某个 shard 中
             */
            producer.send(project, logStore, "topic", "", "logStore", logItem, result -> {
                if (result.isSuccessful()) {
                    log.info("日志发送成功");
                } else {
                    log.error("日志发送失败, project={}, logStore={}, logItem={}, result={}", project, logStore, logItem.ToJsonString(), result);
                }
            });
        } catch (InterruptedException e) {
            log.warn("当前线程在发送日志期间被中断", e);
        } catch (MaxBatchCountExceedException me) {
            log.error("日志超过了最大批量计数:", me);
        } catch (LogSizeTooLargeException lse) {
            log.error("日志大小超过了设置的最大值:", lse);
        } catch (TimeoutException te) {
            log.error("为日志分配内存所花费的时间已超时:", te);
        } catch (Exception e) {
            log.error("发送日志失败, 错误为:", e);
        } finally {
            if (producer != null) {
                try {
                    producer.close();
                } catch (InterruptedException e) {
                    log.warn("当前线程已被关闭中断：", e);
                } catch (Exception e) {
                    log.info("关闭 producer异常：", e);
                }
            }
        }
        log.info("send sls log finish");
    }
    /**
     * 发送日志
     *
     * @param map
     */
    public static void sendLog(Map<String, Object> map, String project, String logStore, String endpoint, String accessKeyId, String accessKeySecret) {
        if (map.isEmpty()) {
            log.info("发送的日志不能为空");
            return;
        }
        // 包装参数
        LogItem logItem = generateLogItem(map);
        sendLog(logItem, project, logStore, endpoint, accessKeyId, accessKeySecret);
    }

    /**
     * 将map类型的参数包装到logItem
     *
     * @param map
     * @return
     */
    private static LogItem generateLogItem(Map<String, Object> map) {
        LogItem logItem = new LogItem();
        map.forEach((key, value) -> logItem.PushBack(key, null != value ? value.toString() : null));
        return logItem;
    }


    /**
     * List集合批量发送
     *
     * @param list
     * @return
     */
    private static List<LogItem> generateLogItems(List<HashMap> list) {
        ArrayList<LogItem> logItems = new ArrayList<>();
        for (HashMap map : list) {
            LogItem logItem = generateLogItem(map);
            logItems.add(logItem);
        }
        return logItems;
    }

    /**
     * 将 EventPutDTO 及其父类 EventPutReqDTO 的字段逐个添加到 LogItem 中
     * 使用传统方式逐个添加logItem字段，效率高，反射和JSON转换好维护但效率慢
     * 这里的字段不常修改，所以使用这种传统方式
     * 如果有新增字段，这里要添加对应的字段处理
     */
    private static LogItem generateLogItem(EventPutDTO eventPutDTO) {
        LogItem logItem = new LogItem();

        // 父类 EventPutReqDTO 的字段
        if (eventPutDTO.getEventType() != null) {
            logItem.PushBack("eventType", eventPutDTO.getEventType());
        }
        if (eventPutDTO.getEventDesc() != null) {
            logItem.PushBack("eventDesc", eventPutDTO.getEventDesc());
        }
        if (eventPutDTO.getDetail() != null) {
            logItem.PushBack("detail", eventPutDTO.getDetail());
        }
        if (eventPutDTO.getEventTime() != null) {
            logItem.PushBack("eventTime", DateUtil.format(eventPutDTO.getEventTime(),"yyyy-MM-dd HH:mm:ss"));
        }
        if (eventPutDTO.getMachineId() != null) {
            logItem.PushBack("machineId", eventPutDTO.getMachineId().toString());
        }
        if (eventPutDTO.getUserId() != null) {
            logItem.PushBack("userId", eventPutDTO.getUserId().toString());
        }
        if (eventPutDTO.getUserPhone() != null) {
            logItem.PushBack("userPhone", eventPutDTO.getUserPhone());
        }
        if (eventPutDTO.getOrderNo() != null) {
            logItem.PushBack("orderNo", eventPutDTO.getOrderNo());
        }
        if (eventPutDTO.getMessage() != null) {
            logItem.PushBack("message", eventPutDTO.getMessage());
        }
        if (eventPutDTO.getBusinessInfo() != null) {
            logItem.PushBack("businessInfo", eventPutDTO.getBusinessInfo());
        }

        // 子类 EventPutDTO 自身字段
        if (eventPutDTO.getAppId() != null) {
            logItem.PushBack("appId", eventPutDTO.getAppId());
        }
        if (eventPutDTO.getSessionId() != null) {
            logItem.PushBack("sessionId", eventPutDTO.getSessionId());
        }
        if (eventPutDTO.getTraceId() != null) {
            logItem.PushBack("traceId", eventPutDTO.getTraceId());
        }
        if (eventPutDTO.getBrandId() != null) {
            logItem.PushBack("brandId", eventPutDTO.getBrandId().toString());
        }
        if (eventPutDTO.getAccountId() != null) {
            logItem.PushBack("accountId", eventPutDTO.getAccountId().toString());
        }
        if (eventPutDTO.getPutType() != null) {
            logItem.PushBack("putType", eventPutDTO.getPutType());
        }
        if (eventPutDTO.getUri() != null) {
            logItem.PushBack("uri", eventPutDTO.getUri());
        }
        if (eventPutDTO.getRequestParams() != null) {
            logItem.PushBack("requestParams", eventPutDTO.getRequestParams());
        }
        if (eventPutDTO.getRequestBody() != null) {
            logItem.PushBack("requestBody", eventPutDTO.getRequestBody());
        }
        if (eventPutDTO.getResponseBody() != null) {
            logItem.PushBack("responseBody", eventPutDTO.getResponseBody());
        }

        return logItem;
    }

    public static void main(String[] args) {

        Map<String, Object> map = new HashMap<>();
        map.put("machineLat", 27.120421666666665);
        map.put("adAccountId", 11113);
        map.put("orderNO", "202201204311606674");
        map.put("phoneLat", 27.116293);
        map.put("helmet", "-1");
        map.put("num", 1);
        map.put("machinePark", 3501);
        map.put("userCode", "80256943");
        map.put("phoneLon", 114.987723);
        map.put("result", 1);
        map.put("phonePark", 3501);
        map.put("parkPointId", 3501);
        map.put("accountId", 11094);
        map.put("machineNO", "009424530");
        map.put("machineLon", 114.98809666666666);
        map.put("extraMsg", "{\"UID\":\"57:E2:0E:3A:08:01:04:E0\",\"cnt\":1,\"dt\":1642676594824,\"extendType\":2,\"nfcControlType\":482,\"ret\":1,\"serNo\":\"96ed3663-f548-4b6c-9181-a23bac3dee82\",\"siteId\":\"01:01:00:00\"}");
        // sendLog(map);


    }
}

